/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment
 * class ListView
 * 
 * Generic view for rendering list items 
 **/

var ListView = Class.create({
	initialize: function(delegate)
	{
		this.delegate = delegate;
		this.surface = Builder.node('div', {className: 'datacontainer'});
		this.list = Builder.node('ul', {className: 'data', style: 'position: relative'});
		this.surface.insert(this.list);
		
		this.surface.observe('mousedown', this.singleClick.bindAsEventListener(this));
		
		this.selected = new Object();
		this.selectedCount = 0;
		this.currentIndex = 0;
	},

/**
 * ListView#singleClick(event) -> null
 * - event (Event): Click Event
 *  
 * Click Event handler
 **/
	
	singleClick: function(event)
	{
		var elem = event.target;
		
		if(elem.tagName != 'LI')
		{
			elem = elem.up('li');
			
			if(!elem)
				return;
		}
			
		if(this.rowIsSelected(elem))
		{
			if(event.metaKey || event.ctrlKey)
				this.deselectRow(elem);
		}
		else
			this.selectRow(elem, (event.metaKey || event.ctrlKey));
	},

/**
 * ListView#selectRow(li, addSelection) -> null
 * - li (HTMLDom): The <li> item to select
 * - addSelection (Boolean): Should the li be added to the current selection
 * 
 * Select a row
 **/
	
	selectRow: function(li, addSelection)
	{		
		if(!addSelection)
		{
			for(i in this.selected)
			{
				if(this.selected[i] && this.list.childElements()[i])
					this.list.childElements()[i].removeClassName('selected');
			}
			
			this.selected = new Object();
			this.selectedCount = 0;
		}
	
		this.selected[li.getIndex()] = true;
		this.selectedCount++;
		this.currentIndex = li.getIndex();
		
		li.addClassName('selected');
	},

/**
 * ListView#deselectRow(li) -> null
 * - li (HTMLDom): The <li> to deselect
 * 
 * Deselect a row
 **/
	
	deselectRow: function(li)
	{
		if(this.selectedCount == 1)
			return;
			
		this.selected[li.getIndex()] = false;
		this.selectedCount--;
		li.removeClassName('selected');
	},

/**
 * ListView#keyPress(event) -> null
 * - event (Event): KeyPress Event
 *  
 * KeyPress Event handler
 **/
	
	keyPress: function(event)
	{
		if(event.keyCode == 38 /* UP */ || event.keyCode == 40 /* DOWN */)
		{
			event.stop();
			
			// Move up or down
			var prev = this.currentIndex;
			this.currentIndex += (event.keyCode==38 ? -1 : 1);
			
			// range check
			if(this.currentIndex < 0)
				this.currentIndex = 0;
				
			if(this.currentIndex >= this.list.childElements().length)
				this.currentIndex = this.list.childElements().length-1;
			
			if(event.shiftKey)
			{
				var c_li = this.list.childElements()[this.currentIndex];
				if(c_li.hasClassName('selected'))
				{
					var p_li = this.list.childElements()[prev];
					this.deselectRow(p_li);
				}
			}

			var li = this.list.childElements()[this.currentIndex];
			this.selectRow(li, event.shiftKey);
			
			// Make sure the new item is visible
			if(li.offsetTop + li.getHeight() > this.surface.getHeight() + this.surface.scrollTop)
			{
				this.surface.scrollTop = li.offsetTop + li.getHeight() - this.surface.getHeight();
			}
			else if(li.offsetTop < this.surface.scrollTop)
			{
				this.surface.scrollTop = li.offsetTop;
			}
		}
	},

/**
 * ListView#rowIsSelected(elem) -> Boolean
 * - elem (HTMLDom): The <li> to test
 *  
 * Is the <li> item currently selected
 **/
	
	rowIsSelected: function(elem)
	{
		return this.selected[elem.getIndex()];
	},

/**
 * ListView#getSelectedIndexes() -> Object
 *  
 * Get the key/value hash of the selected items
 **/
	
	getSelectedIndexes: function()
	{
		return this.selected;
	},

/**
 * ListView#getValidSelectedIndexes() -> Object
 *  
 * Get the list of the selected items indexes
 **/
	
	getValidSelectedIndexes: function()
	{
		var valid_items = {};
		
		for(var index in this.selected)
		{					
			if(this.selected[index] && !this.list.childElements()[index].hasClassName('grouped'))
			{
				valid_items[index] = true;
			}
		}
		
		return valid_items;
	},

/**
 * ListView#getSelectedElements() -> Array
 *  
 * Get an array of the selected elements
 **/
	
	getSelectedElements: function(indexes)
	{
		if(Object.isUndefined(indexes))
			indexes = this.selected;
			
		var items = [];
		
		for(var index in indexes)
		{					
			if(indexes[index])
				items.push(this.list.childElements()[index]);
		}
		
		return items;
	},

/**
 * ListView#getValidSelectedElements() -> Array
 *  
 * Get an array of all the currently selected elements
 **/
	
	getValidSelectedElements: function()
	{
		return this.getSelectedElements(this.getValidSelectedIndexes());
	},

/**
 * ListView#render() -> null
 *  
 * Render the list
 **/
	
	render: function()
	{
		var newList = Builder.node('ul', {className: 'data', style: 'position: relative'});
		var i=0;
		this.delegate.data.each(function(item){
			var li = this.buildListItem(item, i);
			
			if(li)
			{
				newList.insert(li);
				i++;
			}
		}.bind(this));
		
		this.list.replace(newList);
		this.list = newList;
		
		if(this.delegate.data.length)
			this.selectRow(this.list.childElements()[0]);
	},

/**
 * ListView#buildListItem(item, index) -> HTMLDom
 * - item (Object): The item to render
 * - index (Integer): The index of the item in the list
 *  
 * Helper function to render a single <li> item
 **/
	
	buildListItem: function(item, index)
	{
		if(!item.id || !item.label)
			return false;
			
		var li = Builder.node('li', {id: item.id+'-'+index, className: item.id}, item.label);
		li.getIndex = function() {
			return parseInt(this.id.split('-').last()); 
		};
		li.getID = function() {
			return this.id.split('-').first(); 
		};
		li.setLoading = function(loading) {
			try
			{
				if(Object.isUndefined(loading))
					loading = true;
				
				if(loading && !this.down('.loading'))
				{
					this.insert(Builder.node('div', {className: 'loading'}));
				}
				else
				{
					var elem = this.down('.loading');
				
					if(elem)
						elem.remove();
				}
			}
			catch(e)
			{}
		};
		li.setDisabled = function() {
			this.setLoading(false);
			this.addClassName('grouped');
			new Effect.Fade(this, {
				to: 0.2
			})
		};
		index % 2 == 0 ? li.addClassName('odd') : li.addClassName('even');
		
		return li;
	},

/**
 * ListView#setLoading(loading) -> null
 * - loading (Boolean): Whether the loading indication should be enabled/disabled 
 * 
 * Set the state of the loading indicator
 **/
	
	setLoading: function(loading)
	{
		if(Object.isUndefined(loading))
			loading = true;
			
		if(loading)
		{
			this.list.update();
			this.list.insert(Builder.node('li', {className: 'loading'}, 'Loading'));
		}
		else
		{

		}
	},

/**
 * ListView#scrollToID(id) -> null
 * - id (Integer): The id of the item to scroll into view
 *  
 * Scroll to the item with the given ID
 **/
	
	scrollToID: function(id)
	{
		var item = this.surface.down('.'+id);
		
		var offset_top = item.offsetTop;
		
		var surface_height = this.surface.getHeight();
		
		var total_offset = (offset_top - (0.5*surface_height)) + item.getHeight();
		
		this.surface.scrollTop = total_offset;
		
		Effect.Pulsate(item, {
			pulses: 2,
			duration: 1
		})
	},

/**
 * ListView#getSurface() -> null
 *  
 * Gets the drawing surface for this object
 **/
	
	getSurface: function()
	{
		return this.surface;
	}
})